//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//    InstallerCustomActions.cpp
//
// Abstract:
//    
//    Defines the exported functions for the DLL application
//
//--

#include "stdafx.h"
#include "InstallerCustomActions.h"

// Max size of strings used in this module
#define MAX_STR_LEN 256

#define MAX_NO_ACE 3

// Paths to the MSMQs used by Enhanced Reminders
WCHAR* pwszSendQueuePath = L".\\Private$\\EnhancedRemindersNotifications";
WCHAR* pwszResponseQueuePath = L".\\Private$\\EnhancedRemindersResponses";
WCHAR* pwszAdministrationQueuePath = L".\\Private$\\EnhancedRemindersAdministrationQueue";


/////////////////////////////////////////////////////////////////////////////
//
// Function:  MsiLog
//            Helper method to log messages into the installer log.
//
/////////////////////////////////////////////////////////////////////////////
void MsiLog(MSIHANDLE hInstall, __in LPWSTR message)
{
    PMSIHANDLE hRecord = MsiCreateRecord(1);
    // field 0 is the template
    MsiRecordSetString(hRecord, 0, L"InstallerCustomActionsLog : [1]"); 
    // field 1, to be placed in [1] placeholder
    MsiRecordSetString(hRecord, 1, message); 
    // send message to running installer
    MsiProcessMessage(hInstall, INSTALLMESSAGE_INFO, hRecord); 
}

/////////////////////////////////////////////////////////////////////////////
//
// Function:  MsiLog
//            Helper method to log error messages into the installer log.
//
/////////////////////////////////////////////////////////////////////////////
DWORD MsiLogError(MSIHANDLE hInstall, __in LPWSTR message)
{
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); // Call GetLastError before doing anything else so that the error is not overwritten

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, /* lpSource */
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
        (LPWSTR) &lpMsgBuf,
        0, /* nSize */
        NULL /* va_list */);

    // Log error message 
    MsiLog(hInstall, message);

    // Log error details 
    MsiLog(hInstall, (LPWSTR)lpMsgBuf);

    // Free the buffer
    LocalFree( lpMsgBuf );

    return dw;
}

/////////////////////////////////////////////////////////////////////////////
//
// Function:  GetMsiText
//            Helper method to get text string from MSI database.
//
/////////////////////////////////////////////////////////////////////////////
UINT GetMsiText(MSIHANDLE hInstall, __out LPWSTR *ppwszText, LPCWSTR pwszID, LPDWORD pcchValueBuf)
{
    // Initialize out params
    *pcchValueBuf = 0;
    *ppwszText = NULL;

    WCHAR szEmpty[1] = L"";
    // MsiGetPropertyW returns the number of WCHARs required for the property,
    // not including the terminating null, if passed an empty string.
    UINT uiStat = MsiGetPropertyW(hInstall, pwszID, szEmpty, pcchValueBuf);
    
    if (ERROR_MORE_DATA == uiStat)
    {
        ++(*pcchValueBuf); // on output does not include terminating null, so add 1
        *ppwszText = new WCHAR[*pcchValueBuf];
    
        if (*ppwszText)
        {
            uiStat =  MsiGetPropertyW(hInstall, pwszID, *ppwszText, pcchValueBuf);
        }
    }

    if (uiStat != ERROR_SUCCESS || *ppwszText == NULL)
    {
        if (*ppwszText != NULL) 
        {
            delete [] *ppwszText;
        }

        MsiLog(hInstall, L"Error retrieving localized string from MSI");

        return ERROR_INSTALL_FAILURE;
    }

    return uiStat;
}

/////////////////////////////////////////////////////////////////////////////
//
// Function:  GetMsiText
//            Helper method to get text string from MSI database.
//
/////////////////////////////////////////////////////////////////////////////
UINT GetMsiText(MSIHANDLE hInstall, __in LPWSTR *ppwszText, LPCWSTR pwszID)
{
    DWORD cchValueBuf;

    return GetMsiText(hInstall, ppwszText, pwszID, &cchValueBuf);
}

/////////////////////////////////////////////////////////////////////////////
//
// Function:  CreateMSMQQueue
//            Creates the specified MSMQ with the specified SD.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CreateMSMQQueue(MSIHANDLE hInstall, __in LPWSTR wszPathName, PSECURITY_DESCRIPTOR pSecurityDescriptor)
{
  // Define the maximum number of queue properties.
  const int NUMBEROFPROPERTIES = 2;

  // Define a queue property structure and the structures needed to initialize it.
  MQQUEUEPROPS   QueueProps;
  MQPROPVARIANT  aQueuePropVar[NUMBEROFPROPERTIES];
  QUEUEPROPID    aQueuePropId[NUMBEROFPROPERTIES];
  HRESULT        aQueueStatus[NUMBEROFPROPERTIES];
  HRESULT        hr = MQ_OK;
  
  // Set queue properties.
  DWORD cPropId = 0;
  aQueuePropId[cPropId] = PROPID_Q_PATHNAME;
  aQueuePropVar[cPropId].vt = VT_LPWSTR;
  aQueuePropVar[cPropId].pwszVal = wszPathName;
  cPropId++;

  WCHAR wszLabel[MQ_MAX_Q_LABEL_LEN] = L"Microsoft Enhnaced Reminders Queue";
  aQueuePropId[cPropId] = PROPID_Q_LABEL;
  aQueuePropVar[cPropId].vt = VT_LPWSTR;
  aQueuePropVar[cPropId].pwszVal = wszLabel;
  cPropId++;

  // Initialize the MQQUEUEPROPS structure.
  QueueProps.cProp = cPropId;               // Number of properties
  QueueProps.aPropID = aQueuePropId;        // IDs of the queue properties
  QueueProps.aPropVar = aQueuePropVar;      // Values of the queue properties
  QueueProps.aStatus = aQueueStatus;        // Pointer to the return status

  // Call MQCreateQueue to create the queue.
  WCHAR wszFormatName[MAX_STR_LEN];
  DWORD dwFormatNameLength = sizeof(wszFormatName)/sizeof(wszFormatName[0]);
  hr = MQCreateQueue(pSecurityDescriptor,   // Security descriptor
                     &QueueProps,           // Address of queue property structure
                     wszFormatName,         // Pointer to format name buffer
                     &dwFormatNameLength);  // Pointer to receive the queue's format name length

  WCHAR wszLogMessage[MAX_STR_LEN];

  if (hr == MQ_OK)
  {
      StringCchPrintfW(wszLogMessage, MAX_STR_LEN, L"Created %s", wszPathName);
  }
  else if (hr == MQ_ERROR_QUEUE_EXISTS)
  {
      StringCchPrintfW(wszLogMessage, MAX_STR_LEN, L"Failed to create %s. Queue already exists. Ignoring failure.", wszPathName);

	  // Ignore this error because it most likely means that there was a previous failed uninstall that left the queue behind
	  hr = ERROR_SUCCESS;
  }
  else
  {
      StringCchPrintfW(wszLogMessage, MAX_STR_LEN, L"Failed to create %s", wszPathName);
  }

  MsiLog(hInstall, wszLogMessage);

  return hr;
}

/////////////////////////////////////////////////////////////////////////////
//
// Function:  DeleteMSMQQueue
//            Deletes the specified MSMQ.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT DeleteMSMQQueue(MSIHANDLE hInstall, __in LPWSTR wszPathName)
{
  HRESULT hr = MQ_OK;
  WCHAR wszFormatName[MAX_STR_LEN];
  DWORD dwFormatNameLength = sizeof(wszFormatName)/sizeof(wszFormatName[0]);

  // Need to get the format name of the queue in order to delete it
  hr = MQPathNameToFormatName(wszPathName, wszFormatName, &dwFormatNameLength);

  if (hr == MQ_OK)
  {
      // Delete the specified queue
      hr = MQDeleteQueue(wszFormatName);
  }

  WCHAR wszLogMessage[MAX_STR_LEN];

  if (hr == MQ_OK)
  {
      StringCchPrintfW(wszLogMessage, MAX_STR_LEN, L"Deleted %s", wszPathName);
  }
  else
  {
      StringCchPrintfW(wszLogMessage, MAX_STR_LEN, L"Failed to delete %s", wszPathName);
  }

  MsiLog(hInstall, wszLogMessage);

  return hr;
}

/////////////////////////////////////////////////////////////////////////////
//
// Function:  fnInstallMSMQs
//            Installs the MSMQs used by Enhanced Reminders.
//
/////////////////////////////////////////////////////////////////////////////
extern "C" INSTALLERCUSTOMACTIONS_API UINT fnInstallMSMQs(MSIHANDLE hInstall)
{
    PSECURITY_DESCRIPTOR pSD = NULL;
    PSID pAdminSid = NULL;
    PACL pACL = NULL;
    EXPLICIT_ACCESS_W ea[MAX_NO_ACE];
    SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;

    MsiLog(hInstall, L"Installing MSMQ queues");
        
    // Initialize an EXPLICIT_ACCESS structure for an ACE
    ZeroMemory(ea, MAX_NO_ACE * sizeof(EXPLICIT_ACCESS_W));

	DWORD result = ERROR_SUCCESS;

	// Create a SID for the BUILTIN\Administrators group and Network Service
    if (!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, SECURITY_NETWORK_SERVICE_RID, 0, 0, 0, 0, 0, &pAdminSid)) 
    {
        result = MsiLogError(hInstall, L"AllocateAndInitializeSid failed for the BUILTIN\\Administrators group.");
    }

    if (result == ERROR_SUCCESS)
    {
        pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); 

        if (pSD == NULL) 
        { 
            result = MsiLogError(hInstall, L"LocalAlloc failed.");
        } 
    }

    if (result == ERROR_SUCCESS)
    {
        // Initialize a security descriptor
        if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) 
        {  
            result = MsiLogError(hInstall, L"InitializeSecurityDescriptor failed.");
        } 
    }

    if (result == ERROR_SUCCESS)
    {
        // Add the ACL to the security descriptor. 
        if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE))
        {  
            result = MsiLogError(hInstall, L"SetSecurityDescriptorDacl failed.");
        } 
    }

    // Create the send queue
    if (result == ERROR_SUCCESS)
    {
        result = CreateMSMQQueue(hInstall, pwszSendQueuePath, pSD);
    }

    // Create the response queue
    if (result == ERROR_SUCCESS)
    {
        result = CreateMSMQQueue(hInstall, pwszResponseQueuePath, pSD);
    }

    // Create the administration queue
    if (result == ERROR_SUCCESS)
    {
        result = CreateMSMQQueue(hInstall, pwszAdministrationQueuePath, pSD);
    }

    // Clean up
    //
    if (pAdminSid != NULL) 
    {
        FreeSid(pAdminSid);
    }

    if (pACL != NULL) 
    {
        LocalFree(pACL);
    }

    if (pSD != NULL) 
    {
        LocalFree(pSD);
    }

    return result;
}

/////////////////////////////////////////////////////////////////////////////
//
// Function:  fnUninstallMSMQs
//            Uninstalls the MSMQs used by Enhanced Reminders.
//
/////////////////////////////////////////////////////////////////////////////
extern "C" INSTALLERCUSTOMACTIONS_API UINT fnUninstallMSMQs(MSIHANDLE hInstall)
{
    DWORD result = ERROR_SUCCESS;

    MsiLog(hInstall, L"Uninstalling MSMQ queues");
        
    // Delete the send queue
    DeleteMSMQQueue(hInstall, pwszSendQueuePath);

    // Delete the response queue
    DeleteMSMQQueue(hInstall, pwszResponseQueuePath);

    // Delete the administration queue
    DeleteMSMQQueue(hInstall, pwszAdministrationQueuePath);

    // Ignore failures deleting queues
    return ERROR_SUCCESS;
}
